#ifndef COMMAND_H
#define COMMAND_H

#define _CRT_SECURE_NO_DEPRECATE
#pragma warning(disable:4996)

#include "operations.h"
#include "Writer.h"
#include <iostream>
#include <string>
#include <vector>
#include <map>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::map;

class command
{
    int flag;
    Dao::BaseDao _dao;
    Dao::ValueDao vdao;
    Dao::ConnectDao cdao;

    void init(const char* url);
    void create();
    void destroy();
    void prepare(string& command);
    void sql();
    void select(vector<string>& v);
    void insert(vector<string>& v);
    void Delete(vector<string>& v);
    void update(vector<string>& v);
    void resize();
    void help();
    void csv();
    void print(string url, vector<entity::Record> record);
public:
    command(const char* url = "pm.db");
    ~command();
    void requireCommand();
};

inline void command::init(const char * url)
{
    if (!func::exist(url))
    {
        FILE *file;
        file = fopen(url, "w");
        fclose(file);
        _dao.init(url);
        create();
        _dao.destroy();
    }
    if (!func::exist("help.txt"))
    {
        FILE *file;
        file = fopen(url, "w");
        fprintf(file, "Can't find help document.\n");
        fclose(file);
    }
}

inline command::command(const char* url) :flag(0)
{
    init(url);
    _dao.init(url);
    vdao.init(_dao);
    cdao.init(_dao);
    cdao.setValueDao(vdao);
}

inline command::~command()
{
    _dao.destroy();
}

inline void command::requireCommand()
{
    string s;
    while (flag == 0)
    {
        printf("%s", "Password Management>");
        std::getline(cin, s);
        prepare(s);
        printf("\n");
    }
}

inline void command::prepare(string& command)
{
    command = func::trim(command);
    try
    {
        if ("sql" == func::lower(command))
        {
            sql();
            return;
        }
        if ("resize" == func::lower(command))
        {
            resize();
            return;
        }
        if ("csv" == func::lower(command))
        {
            csv();
            return;
        }
        else if ("help" == func::lower(command))
        {
            help();
            return;
        }
        else if (("quit" == func::lower(command))||("exit" == func::lower(command)))
        {
            flag = -1;
            return;
        }
        vector<string> v;
        func::split(command, "-", v);

        if (func::find_command(v, "select")) select(v);
        else if (func::find_command(v, "insert")) insert(v);
        else if (func::find_command(v, "delete")) Delete(v);
        else if (func::find_command(v, "update")) update(v);
        else
        {
            printf("%s", "Error Command!!!\n");
        }
    }
    catch (const char* e)
    {
        printf("%s", e);
    }
}

inline void command::create()
{
    string sql = "create table key ( id integer primary key autoincrement not null, value varchar(64) not null on conflict ignore, ref integer not null default 0, constraint kvalue unique(value asc) on conflict ignore);create table account(id integer primary key autoincrement not null, value varchar(64) not null on conflict ignore, ref integer not null default 0, constraint avalue unique(value asc) on conflict ignore);create table password(id integer primary key autoincrement not null, value varchar(64) not null on conflict ignore, ref integer not null default 0, constraint pvalue unique(value asc) on conflict ignore);create table record(id integer primary key autoincrement not null, key text not null, account text not null, password integer not null);";
    _dao.execute(sql);
}

inline void command::destroy()
{
    string sql = "drop table if exists key;drop table if exists account;drop table if exists password;drop table if exists record;";
    _dao.execute(sql);
}

inline void command::sql()
{
    system("sqlite3.exe pm.db");
}

inline void command::select(vector<string>& v)
{
    try
    {
        map<string, string> orders = func::analysis(v, "select");
        if (orders.size() == 0) {
            printf("%s", "Error Command!!!\n");
            return;
        }
        bool isfoggy = func::mode(orders);
        auto it = orders.find("id");
        if (it != orders.end())
        {
            orders.erase("id");
            it = func::find_key(orders);
            if (it->first == "record")
            {
                auto c = cdao.select_id(func::atoI(it->second));
                cout << Dao::convert(c, vdao) << endl;
            }
            else
            {
                cout << vdao.select_id(it->first, func::atoI(it->second)) << endl;
            }
        }
        else
        {
            it = orders.find("record");
            if (it == orders.end())
            {
                it = func::find_key(orders);
                if (isfoggy)
                {
                    vdao.select_like(it->first, it->second).print_result();
                }
                else
                {
                    cout << vdao.select_value(it->first, it->second) << endl;
                }
            }
            else
            {
                orders.erase("record");
                if (orders.size() > 0)
                {
                    map<string, vector<int> > m;
                    m.insert(map<string, vector<int> >::value_type("key", vector<int>()));
                    m.insert(map<string, vector<int> >::value_type("account", vector<int>()));
                    m.insert(map<string, vector<int> >::value_type("password", vector<int>()));
                    for (auto& order : orders)
                    {
                        vector<string> values;
                        func::split(order.second, ",", values);
                        for (auto& value : values)
                        {
                            if (isfoggy)
                            {
                                auto temp = vdao.select_like(order.first, value);
                                if (temp.size() == 0)
                                {
                                    throw "can't find " + order.first + " with " + value + " in value.\n";
                                }
                                else
                                {
                                    vector<entity::KeyValue> stack = temp.get_result();
                                    for (auto& i : stack)
                                    {
                                        m[order.first].push_back(i.id);
                                    }
                                }
                            }
                            else
                            {
                                auto temp = vdao.select_value(order.first, value);
                                m[order.first].push_back(temp.id);
                            }
                        }
                    }
                    func::unique(m["key"]);
                    func::unique(m["account"]);
                    func::unique(m["password"]);
                    auto stack = cdao.select_like(m["key"], m["account"], m["password"]).get_result();
                    for (auto& c : stack)
                    {
                        cout << Dao::convert(c, vdao) << endl;
                    }
                }
                else
                {
                    auto stack = cdao.select("").get_result();
                    for (auto& c : stack)
                    {
                        cout << Dao::convert(c, vdao) << endl;
                    }
                }
            }
        }
    }
    catch (const char* e) {
        printf("%s", e);
        vdao.rollback();
    }
    catch (string e) {
        std::cout << e;
        vdao.rollback();
    }
}

inline void command::insert(vector<string>& v)
{
    vdao.begin();
    try
    {
        map<string, string> orders = func::analysis(v, "insert");
        if (orders.size() == 0) {
            printf("%s", "Error Command!!!\n");
            return;
        }
        auto it = orders.find("record");
        if (it == orders.end() || it->second == string())
        {
            it = orders.find("record");
            if (it == orders.end())
            {
                string sql;
                for (auto& order : orders)
                {
                    vector<string> values;
                    func::split(order.second, ",", values);
                    for (auto& value : values)
                    {
                        vdao.insert(order.first, value);
                    }
                }
            }
            else
            {
                orders.erase("record");
                if ((orders.find("key") != orders.end())
                    && (orders.find("account") != orders.end())
                    && (orders.find("password") != orders.end())
                    )
                {
                    map<string, vector<int> > m;
                    m.insert(map<string, vector<int> >::value_type("key", vector<int>()));
                    m.insert(map<string, vector<int> >::value_type("account", vector<int>()));
                    m.insert(map<string, vector<int> >::value_type("password", vector<int>()));
                    for (auto& order : orders)
                    {
                        vector<string> values;
                        func::split(order.second, ",", values);
                        if (order.first == "password"&&values.size() != 1)
                        {
                            throw "password attribute only allows one value.\n";
                        }
                        else
                        {
                            for (auto& value : values)
                            {
                                m[order.first].push_back(vdao.insert(order.first, value).id);
                            }
                        }
                    }
                    func::unique(m["password"]);
                    cdao.insert(m["key"], m["account"], m["password"][0]);
                }
                else {
                    throw "too few parameters.\n";
                }
            }
        }
        else
        {
            orders.erase("password");
            it = orders.find("record");
            if (it != orders.end() && orders.size() > 1)
            {
                int id = func::atoI(it->second);
                orders.erase("record");
                auto c = cdao.select_id(id);
                map<string, vector<int> > m;
                m.insert(map<string, vector<int> >::value_type("key", vector<int>()));
                m.insert(map<string, vector<int> >::value_type("account", vector<int>()));
                for (auto& order : orders)
                {
                    vector<string> values;
                    func::split(order.second, ",", values);
                    for (auto& value : values)
                    {
                        m[order.first].push_back(vdao.insert(order.first, value).id);
                    }
                }
                cdao.insert_connect(id, m["key"], m["account"]);
            }
            else {
                throw "too few parameters.\n";
            }
        }
    }
    catch (const char* e) {
        printf("%s", e);
        vdao.rollback();
    }
    catch (string e) {
        std::cout << e;
        vdao.rollback();
    }
    vdao.commit();
}

inline void command::Delete(vector<string>& v)
{
    vdao.begin();
    try
    {
        map<string, string> orders = func::analysis(v, "delete");
        if (orders.size() == 0) {
            printf("%s", "Error Command!!!\n");
            return;
        }
        auto it = orders.find("id");
        if (it != orders.end())
        {
            orders.erase("id");
            for (auto& order : orders)
            {
                if (order.first == "record")
                {
                    cdao.Delete_id(func::atoI(order.second));
                }
                else
                {
                    vdao.Delete_id(order.first, func::atoI(order.second));
                }
            }
        }
        else
        {
            it = orders.find("record");
            if (it != orders.end())
            {
                if (it->second == string())
                {
                    orders.erase("record");
                    bool isfoggy = func::mode(orders);
                    if (orders.size() > 0)
                    {
                        map<string, vector<int> > m;
                        m.insert(map<string, vector<int> >::value_type("key", vector<int>()));
                        m.insert(map<string, vector<int> >::value_type("account", vector<int>()));
                        m.insert(map<string, vector<int> >::value_type("password", vector<int>()));
                        for (auto& order : orders)
                        {
                            vector<string> values;
                            func::split(order.second, ",", values);
                            if (order.first == "password"&&values.size() != 1)
                            {
                                throw "password attribute only allows one value.\n";
                            }
                            else
                            {
                                for (auto& value : values)
                                {
                                    if (isfoggy)
                                    {
                                        auto stack = vdao.select_like(order.first, value).get_result();
                                        for (auto& i : stack)
                                        {
                                            m[order.first].push_back(i.id);
                                        }
                                    }
                                    else
                                    {
                                        m[order.first].push_back(vdao.select_value(order.first, value).id);
                                    }
                                }
                            }
                        }
                        func::unique(m["key"]);
                        func::unique(m["account"]);
                        func::unique(m["password"]);
                        auto stack = cdao.select_like(m["key"], m["account"], m["password"]).get_result();
                        for (auto& c : stack)
                        {
                            cdao.Delete_id(c.id);
                        }
                    }
                    else {
                        throw "too few parameters.\n";
                    }
                }
                else
                {
                    int id = func::atoI(it->second);
                    orders.erase("record");
                    auto c = cdao.select_id(id);
                    map<string, vector<int> > m;
                    m.insert(map<string, vector<int> >::value_type("key", vector<int>()));
                    m.insert(map<string, vector<int> >::value_type("account", vector<int>()));
                    for (auto& order : orders)
                    {
                        vector<string> values;
                        func::split(order.second, ",", values);
                        for (auto& value : values)
                        {
                            m[order.first].push_back(vdao.select_value(order.first, value).id);
                        }
                    }
                    cdao.Delete_connect(id, m["key"], m["account"]);
                }
            }
            else
            {
                bool isfoggy = func::mode(orders);
                for (auto& order : orders)
                {
                    vector<string> values;
                    func::split(order.second, ",", values);
                    for (auto& value : values)
                    {
                        if (isfoggy)
                        {
                            vdao.Delete_like(order.first, value);
                        }
                        else
                        {
                            vdao.Delete_value(order.first, value);
                        }
                    }
                }
            }
        }
    }
    catch (const char* e) {
        printf("%s", e);
        vdao.rollback();
    }
    catch (string e) {
        std::cout << e;
        vdao.rollback();
    }
    vdao.commit();
}

inline void command::update(vector<string>& v)
{
    vdao.begin();
    try
    {
        map<string, string> orders = func::analysis(v, "update");
        if (orders.size() == 0) {
            printf("%s", "Error Command!!!\n");
            return;
        }
        if (orders.size() == 0) {
            printf("%s", "Error Command!!!\n");
            return;
        }
        auto it = orders.find("record");;
        if (it == orders.end())
        {
            for (auto& order : orders)
            {
                vector<string> values;
                func::split(order.second, "=", values, false);
                if (values.size() != 2)
                {
                    throw "wrong number of parameters.\n";
                }
                else
                {
                    auto old_kv = vdao.select_value(order.first, values[0]);
                    vdao.update_value(order.first, old_kv.id, values[1]);
                }
            }
        }
        else
        {
            int id = func::atoI(it->second);
            orders.erase("record");
            auto c = cdao.select_id(id);
            vector<string> values;
            it = func::find_key(orders);
            func::split(it->second, "=", values, false);
            if (values.size() != 2)
            {
                throw "wrong number of parameters.\n";
            }
            else
            {
                int old_id = 0;
                if (it->first != "password" || values[0] != string())
                {
                    old_id = vdao.select_value(it->first, values[0]).id;
                }
                int new_id = vdao.insert(it->first, values[1]).id;
                cdao.update(id, it->first, old_id, new_id);
            }
        }
    }
    catch (const char* e) {
        printf("%s", e);
        vdao.rollback();
    }
    catch (string e) {
        std::cout << e;
        vdao.rollback();
    }
    vdao.commit();
}

inline void command::resize()
{
    auto stack = cdao.select("").get_result();
    vector<entity::Record> record;
    vector<entity::Record> error;
    int key_width = 0;
    int account_width = 0;
    int e_key_width = 0;
    int e_account_width = 0;
    for (auto& c : stack)
    {
        auto t = Dao::convert(c, vdao);
        if (t.keys.size() == 0 || t.accounts.size() == 0)
        {
            error.push_back(t);
            e_key_width = max(e_key_width, t.keys.size());
            e_account_width = max(e_account_width, t.accounts.size());
        }
        else
        {
            record.push_back(t);
            key_width = max(key_width, t.keys.size());
            account_width = max(account_width, t.accounts.size());
        }
    }
    for (auto& r : record)
    {
        cout << r << endl;
    }
}

inline void command::help()
{
    func::read_file("help.txt");
}

inline void command::csv()
{
    auto stack = cdao.select("").get_result();
    vector<entity::Record> record;
    vector<entity::Record> error;
    for (auto& c : stack)
    {
        auto t = Dao::convert(c, vdao);
        if (t.keys.size() == 0 || t.accounts.size() == 0)
        {
            error.push_back(t);
        }
        else
        {
            record.push_back(t);
        }
    }
    if (record.size() > 0)
    {
        print("result.csv", record);
    }
    if (error.size() > 0)
    {
        print("error.csv", error);
    }
}

inline void command::print(string url, vector<entity::Record> record)
{
    int key_width = 1;
    int account_width = 1;
    for (auto& r : record)
    {
        key_width = max(key_width, r.keys.size());
        account_width = max(account_width, r.accounts.size());
    }
    if (!func::exist(url.c_str()))
    {
        FILE *file;
        file = fopen(url.c_str(), "w");
        fclose(file);
    }
    Writer print(url.c_str());
    for (auto& r : record)
    {
        string s;
        for (auto& key : r.keys)
        {
            s += key.value + ',';
        }
        for (int w = key_width - r.keys.size(); w > 0; w--)
        {
            s += ',';
        }
        for (auto& account : r.accounts)
        {
            s += account.value + ',';
        }
        for (int w = account_width - r.accounts.size(); w > 0; w--)
        {
            s += ',';
        }
        s += r.password.value;
        print.write(s);
    }
    print.close();
}

#endif
